package cn.itzzx.lagou.mybatis.session;

import cn.itzzx.lagou.mybatis.pojo.BoundSql;
import cn.itzzx.lagou.mybatis.pojo.Configuration;
import cn.itzzx.lagou.mybatis.pojo.MappedStatement;
import cn.itzzx.lagou.mybatis.utils.GenericTokenParser;
import cn.itzzx.lagou.mybatis.utils.ParameterMapping;
import cn.itzzx.lagou.mybatis.utils.ParameterMappingTokenHandler;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 请求参数封装，返回结果封装，以及执行sql
 *
 * @author Martin
 * @date 2020-04-25
 * @see 1.0
 */
public class SimpleExecutor implements Executor {

    /**
     * 执行查询操作
     *
     * @param configuration
     * @param mappedStatement
     * @param params
     * @param <E>
     * @return
     */
    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, IntrospectionException {
        Connection connection = configuration.getDataSource().getConnection();
        if (connection == null) {
            throw new RuntimeException("数据库连接失败....");
        }

        // 2 获取sql语句 select * from user where id = #{id} and username = #{username}
        //   转换sql语句 select * from user where id = ? and username = ? 转换的过程中，还需要对 #{} 里面的值进行解析存储 (#id} 这么设计的目的就是根据#{} 里面的值去找到实体类的属性
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);

        // 3 获取预处理对象 : preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        // 4 参数设置

        //传入参数类型
        String parameterType = mappedStatement.getParameterType();
        Class<?> parameterTypeClass = getClassType(parameterType);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

        for (int i = 0; i < parameterMappings.size(); i++) {
            //#{} 里面的字段
            String content = parameterMappings.get(i).getContent();

            //反射 根据参数名称获取实体对象的属性值
            try {
                Field declaredField = parameterTypeClass.getDeclaredField(content);
                //暴力访问 private
                declaredField.setAccessible(true);
                //通过反射获取 params 的值
                Object o = declaredField.get(params[0]);
                //设置请求参数
                preparedStatement.setObject(i + 1, o);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        //执行sql
        ResultSet resultSet = preparedStatement.executeQuery();

        //5 封装返回结果 ResultSet
        //获取返回参数的全路径
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);

        List<Object> result = new ArrayList<>();

        while (resultSet.next()) {
            Object o = resultTypeClass.newInstance();
            //元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                //字段名
                String columnName = metaData.getColumnName(i);
                //字段值
                Object value = resultSet.getObject(columnName);
                //使用反射封装结果集
                //内省库中的类
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o, value);
            }
            result.add(o);
        }

        //封装返回结果

        return (List<E>) result;
    }

    /**
     * 插入 修改 删除 具体操作根据 MappedStatement
     *
     * @param configuration
     * @param mappedStatement
     * @param params
     * @return
     */
    @Override
    public int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        if(mappedStatement.getOperation().startsWith("delete")){
            return doDelete(configuration, mappedStatement, params);
        } else {
            doUpdate(configuration, mappedStatement, params);
        }
        return 0;
    }


    private int doDelete(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        // 获取数据连接
        Connection connection = configuration.getDataSource().getConnection();
        if (connection == null) {
            throw new RuntimeException("数据库连接失败....");
        }
        // 获取sql语句 select * from user where id = #{id} and username = #{username}
        //   转换sql语句 select * from user where id = ? and username = ? 转换的过程中，还需要对 #{} 里面的值进行解析存储 (#id} 这么设计的目的就是根据#{} 里面的值去找到实体类的属性
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);
        // 获取预处理对象：preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        // 根据id删除 ( delete from user where id = #{id} )
        preparedStatement.setInt(1, (int) params[0]);
        // 执行sql
        preparedStatement.execute();
        // 返回sql执行受影响行数
        return preparedStatement.getUpdateCount();
    }

    private int doUpdate(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        // 1. 注册驱动，获取连接
        Connection connection = configuration.getDataSource().getConnection();

        // 2. 获取sql语句 : select * from user where id = #{id} and username = #{username}
        //转换sql语句： select * from user where id = ? and username = ? ，转换的过程中，还需要对#{}里面的值进行解析存储
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);

        // 3.获取预处理对象：preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        // 4. 设置参数
        //获取到了参数的全路径
        String paramterType = mappedStatement.getParameterType();
        Class<?> paramtertypeClass = getClassType(paramterType);

        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();
            //反射
            Field declaredField = paramtertypeClass.getDeclaredField(content);
            //暴力访问
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);
            preparedStatement.setObject(i + 1, o);
        }
        // 5. 执行sql
        preparedStatement.executeUpdate();
        return preparedStatement.getUpdateCount();
    }


    private Class<?> getClassType(String parameterType) throws ClassNotFoundException {
        if (parameterType != null) {
            return Class.forName(parameterType);
        }
        return null;
    }

    /**
     * 完成对 #{}的解析工作
     * 1 将#{}使用 ? 代替
     * 2 解析出#{}里面的值进行存储
     *
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        //将#{} 转化为 ? 的sql
        String parse = genericTokenParser.parse(sql);
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parse, parameterMappings);
        return boundSql;
    }
}
